unit actionpatternsframeunit;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls, Grids, Menus, database, fpjson, Types;

type

  { TActionPatternsFrame }

  TActionPatternsFrame = class(TFrame)
    EditPatternMenuItem: TMenuItem;
    CutPatternMenuItem: TMenuItem;
    CopyPatternMenuItem: TMenuItem;
    DeletePatternMenuItem: TMenuItem;
    SelectAllPatternsMenuItem: TMenuItem;
    PastePatternMenuItem: TMenuItem;
    PatternSeparatorMenuItem: TMenuItem;
    PatternsStringGrid: TStringGrid;
    PatternsPopupMenu: TPopupMenu;
    procedure CopyPatternMenuItemClick(Sender: TObject);
    procedure CutPatternMenuItemClick(Sender: TObject);
    procedure DeletePatternMenuItemClick(Sender: TObject);
    procedure EditPatternMenuItemClick(Sender: TObject);
    procedure PastePatternMenuItemClick(Sender: TObject);
    procedure PatternsPopupMenuPopup(Sender: TObject);
    procedure PatternsStringGridAfterSelection(Sender: TObject; aCol,
      aRow: Integer);
    procedure PatternsStringGridDblClick(Sender: TObject);
    procedure PatternsStringGridDrawCell(Sender: TObject; aCol, aRow: Integer;
      aRect: TRect; aState: TGridDrawState);
    procedure PatternsStringGridKeyUp(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure PatternsStringGridMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure SelectAllPatternsMenuItemClick(Sender: TObject);
  private
    Db: TDatabase;
    EditedPatterns: TJSONArray;
    procedure UpdatePatternCells(Index: Integer; Pattern: TJSONObject);
    function ConditionToText(Pattern: TJSONObject): String;
    function GetFocusedParam: TJSONObject;
    procedure EditFocusedPattern;
    procedure AddNewPattern;
    procedure DeleteSelectedPatterns;
    procedure CopySelectedPatterns;
    procedure PasteFromClipboard;
  public
    procedure SetDatabase(ADb: TDatabase);
    procedure SetPatterns(AEditedPatterns: TJSONArray);
    procedure UpdateGrid;
  end;

resourcestring
  SkillStr = 'Skill';
  ConditionStr = 'Condition';
  RatingStr = 'Rating';
  AddNewStr = '<Add>';
  EditPatternMenuItemStr = 'Edit';
  AddNewPatternMenuItemStr = 'Add new trait';

  CondAlways = 'Always';
  CondTurn = 'Turn is %s';
  CondHp = 'HP = %s ... %s';
  CondMp = 'MP = %s ... %s';
  CondState = 'State is %s';
  CondParty = 'Party level >= %s';
  CondSwitch = '{%s} is ON';
  CondUnknown = '(Unknown condition)';


implementation

uses
  Graphics, Dialogs, constants, actionpatternselection, LCLType, Math,
  clipboardhelper;

{$R *.lfm}

{ TActionPatternsFrame }

procedure TActionPatternsFrame.PatternsStringGridDrawCell(Sender: TObject;
  aCol, aRow: Integer; aRect: TRect; aState: TGridDrawState);
begin
  if aRow <> PatternsStringGrid.RowCount - 1 then
    Exit;

  { Paint text of cell 1,1 clipped to current cell. }
  (*with PatternsStringGrid do begin
    {
    Canvas.Brush.Color := clRed;
    Canvas.Brush.Style := bsSolid;
    Canvas.FillRect(aRect);
    }
    if aCol = 0 then
      Canvas.TextOut(aRect.Left, aRect.Top, '<Add new action pattern>');
  end; *)
end;

procedure TActionPatternsFrame.PatternsStringGridKeyUp(Sender: TObject;
  var Key: Word; Shift: TShiftState);
begin
  if Key = VK_RETURN then
    PatternsStringGridDblClick(Sender);

  if Key = VK_DELETE then begin
    DeleteSelectedPatterns;
    UpdateGrid
  end;

  if (ssCtrl in Shift) and (Key = VK_C) then
    CopyPatternMenuItemClick(Sender);
  if (ssCtrl in Shift) and (Key = VK_V) then
    PastePatternMenuItemClick(Sender);
  if (ssCtrl in Shift) and (Key = VK_X) then
    CutPatternMenuItemClick(Sender);
  if (ssCtrl in Shift) and (Key = VK_A) then
    SelectAllPatternsMenuItemClick(Sender);
end;

procedure TActionPatternsFrame.PatternsStringGridMouseUp(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbRight then
    PatternsPopupMenu.PopUp;
end;

procedure TActionPatternsFrame.SelectAllPatternsMenuItemClick(Sender: TObject);
var
  Range: TGridRect;
begin
  Range.Left := 0;
  Range.Right := 2;
  Range.Top := 1;
  Range.Bottom := EditedPatterns.Count;

  PatternsStringGrid.Selection := Range;
end;

procedure TActionPatternsFrame.PatternsStringGridAfterSelection(
  Sender: TObject; aCol, aRow: Integer);
var
  I: Integer;
  Range: TGridRect;
begin
  PatternsStringGrid.BeginUpdate;

  Range := PatternsStringGrid.Selection;

  Range.Left := 0;
  Range.Right := 2;

  PatternsStringGrid.Selection := Range;

  PatternsStringGrid.EndUpdate(True);
end;

procedure TActionPatternsFrame.PatternsPopupMenuPopup(Sender: TObject);
var
  Pattern: TJSONObject;
begin
  Pattern := GetFocusedParam;
  if Pattern <> nil then
    EditPatternMenuItem.Caption := EditPatternMenuItemStr
  else
    EditPatternMenuItem.Caption := AddNewPatternMenuItemStr;

  PastePatternMenuItem.Enabled := ClipboardHasData('application/rpgmv-actions');
end;

procedure TActionPatternsFrame.EditPatternMenuItemClick(Sender: TObject);
begin
  PatternsStringGridDblClick(Sender);
end;

procedure TActionPatternsFrame.PastePatternMenuItemClick(Sender: TObject);
begin
  PasteFromClipboard;
  UpdateGrid;
end;

procedure TActionPatternsFrame.CutPatternMenuItemClick(Sender: TObject);
begin
  CopySelectedPatterns;
  DeleteSelectedPatterns;
  UpdateGrid;
end;

procedure TActionPatternsFrame.DeletePatternMenuItemClick(Sender: TObject);
begin
  DeleteSelectedPatterns;
  UpdateGrid
end;

procedure TActionPatternsFrame.CopyPatternMenuItemClick(Sender: TObject);
begin
  CopySelectedPatterns
end;

procedure TActionPatternsFrame.PatternsStringGridDblClick(Sender: TObject);
begin
  if GetFocusedParam <> nil then
    EditFocusedPattern
  else
    AddNewPattern;
end;

procedure TActionPatternsFrame.SetDatabase(ADb: TDatabase);
begin
  Db := ADb;

  { TODO: there must be a better way to preset column sizes }
  PatternsStringGrid.AutoFillColumns := True;
  PatternsStringGrid.AutoFillColumns := False;
end;

procedure TActionPatternsFrame.SetPatterns(AEditedPatterns: TJSONArray);
begin
  EditedPatterns := AEditedPatterns;

  UpdateGrid
end;

procedure TActionPatternsFrame.UpdatePatternCells(Index: Integer;
  Pattern: TJSONObject);
var
  SkillId: Integer = 0;
  Rating: Integer = 0;
  SkillIdJson, RatingJson: TJSONNumber;
begin
  if (Pattern <> nil) and Pattern.Find('skillId', SkillIdJson) then
    SkillId := SkillIdJson.AsInteger;
  if (Pattern <> nil) and Pattern.Find('rating', RatingJson) then
    Rating := RatingJson.AsInteger;

  PatternsStringGrid.Cells[0, Index] := Db.GetSkillName(SkillId);
  PatternsStringGrid.Cells[1, Index] := ConditionToText(Pattern);
  PatternsStringGrid.Cells[2, Index] := IntToStr(Rating);
end;

function TActionPatternsFrame.ConditionToText(Pattern: TJSONObject): String;
var
  CondType: Integer = -1;
  Param1: Double = 0;
  Param2: Double = 0;
  CondTypeJson, Param1Json, Param2Json: TJSONNumber;

  function GetTurnCondition: String;
  var
    TurnDesc: String;
  begin
    if (Param1 <> 0) and (Param2 <> 0) then
      TurnDesc := '%d*X + %d'.Format([Round(Param2), Round(Param1)])
    else if (Param2 <> 0) then
      TurnDesc := '%d*X'.Format([Round(Param2)])
    else
      TurnDesc := IntToStr(Round(Param1));

    GetTurnCondition := CondTurn.Format([TurnDesc]);
  end;

  function FormatHpMp(FormatString: String): String;
  begin
    FormatHpMp := FormatString.Format([
               IntToStr(Round(100 * Param1)) + '%',
               IntToStr(Round(100 * Param2)) + '%'
               ]);
  end;

begin
  if (Pattern <> nil) and Pattern.Find('conditionType', CondTypeJson) then
    CondType := CondTypeJson.AsInteger;
  if (Pattern <> nil) and Pattern.Find('conditionParam1', Param1Json) then
    Param1 := Param1Json.AsFloat;
  if (Pattern <> nil) and Pattern.Find('conditionParam2', Param2Json) then
    Param2 := Param2Json.AsFloat;

  case CondType of
    ENEMY_ACTION_CONDITION_ALWAYS:
      ConditionToText := CondAlways;
    ENEMY_ACTION_CONDITION_TURN:
      ConditionToText := GetTurnCondition;
    ENEMY_ACTION_CONDITION_HP:
      ConditionToText := FormatHpMp(CondHp);
    ENEMY_ACTION_CONDITION_MP:
      ConditionToText := FormatHpMp(CondMp);
    ENEMY_ACTION_CONDITION_STATE:
      ConditionToText := CondState.Format([Db.GetStateName(Round(Param1))]);
    ENEMY_ACTION_CONDITION_PARTY_LV:
      ConditionToText := CondParty.Format([IntToStr(Round(Param1))]);
    ENEMY_ACTION_CONDITION_SWITCH:
      ConditionToText := CondSwitch.Format([Db.GetSwitchName(Round(Param1))]);

  else
    ConditionToText := CondUnknown;
  end;
end;

function TActionPatternsFrame.GetFocusedParam: TJSONObject;
begin
  with PatternsStringGrid do
    if (Row >= 1) and (Row < RowCount -1) then
      GetFocusedParam := EditedPatterns.Objects[Row - 1]
    else
      GetFocusedParam := nil;
end;

procedure TActionPatternsFrame.EditFocusedPattern;
var
  Pattern: TJSONObject;
  SkillId: Integer = 1;
  Rating: Integer = 1;
  CondType: Integer = 0;
  CondParam1: Double = 0.0;
  CondParam2: Double = 0.0;

  SkillIdJson, RatingJson, CondTypeJson, CondParam1Json, CondParam2Json:
    TJSONNumber;
begin
  Pattern := GetFocusedParam;
  if Pattern.Find('skillId', SkillIdJson) then
    SkillId := SkillIdJson.AsInteger;
  if Pattern.Find('rating', RatingJson) then
    Rating := RatingJson.AsInteger;
  if Pattern.Find('conditionType', CondTypeJson) then
    CondType := CondTypeJson.AsInteger;
  if Pattern.Find('conditionParam1', CondParam1Json) then
    CondParam1 := CondParam1Json.AsFloat;
  if Pattern.Find('conditionParam2', CondParam2Json) then
    CondParam2 := CondParam2Json.AsFloat;

  with ActionPatternSelectionForm do
    if ShowSelection(SkillId, Rating, CondType, CondParam1, CondParam2, Db) then begin
      Pattern.Integers['skillId'] := SelectedSkillId;
      Pattern.Integers['rating'] := SelectedRating;
      Pattern.Integers['conditionType'] := SelectedCondType;
      Pattern.Floats['conditionParam1'] := SelectedCondParam1;
      Pattern.Floats['conditionParam2'] := SelectedCondParam2;

      PatternsStringGrid.BeginUpdate;
      UpdatePatternCells(PatternsStringGrid.Row, Pattern);
      PatternsStringGrid.EndUpdate(True);
    end;
end;

procedure TActionPatternsFrame.AddNewPattern;
var
  Pattern: TJSONObject;
begin
  with ActionPatternSelectionForm do
    if ShowSelection(1, 1, 0, 0, 0, Db) then begin
      Pattern := TJSONObject.Create([
        'skillId', SelectedSkillId,
        'rating', SelectedRating,
        'conditionType', SelectedCondType,
        'conditionParam1', SelectedCondParam1,
        'conditionParam2', SelectedCondParam2
      ]);
      EditedPatterns.Add(Pattern);
      UpdateGrid;
    end;
end;

procedure TActionPatternsFrame.DeleteSelectedPatterns;
var
  I: Integer;
  Range: TGridRect;
begin
  PatternsStringGrid.BeginUpdate;
  Range := PatternsStringGrid.Selection;

  for I := Min(Range.Bottom, EditedPatterns.Count) downto Range.Top do begin
    EditedPatterns.Delete(I - 1);
  end;
  PatternsStringGrid.EndUpdate(True);
end;

procedure TActionPatternsFrame.CopySelectedPatterns;
var
  ArrSrl: TClipboardArraySerializer;
  I, NumCopied: Integer;
  Range: TGridRect;
begin
  ArrSrl := TClipboardArraySerializer.Create(DataTypeIds[DATA_TYPE_ID_ACTIONS]);
  Range := PatternsStringGrid.Selection;
  try
    NumCopied := 0;
    for I := Range.Top to Min(Range.Bottom, EditedPatterns.Count) do begin
      ArrSrl.AddItem(EditedPatterns.Objects[I - 1]);
      Inc(NumCopied)
    end;

    if NumCopied > 0 then
      ArrSrl.CopyToClipboard
  finally
    ArrSrl.Free;
  end;
end;

procedure TActionPatternsFrame.PasteFromClipboard;
var
  PastedArray: TJSONArray;
  I: Integer;
  StartId: Integer;
begin
  StartId := PatternsStringGrid.Row - 1;

  PastedArray := GetJsonArrayFromClipboard('application/rpgmv-actions');
  if PastedArray = nil then
    Exit;

  try
    for I := 0 to PastedArray.Count -1 do begin
      if PastedArray[I].JSONType = jtObject then begin
        EditedPatterns.Insert(StartId + I, PastedArray.Objects[I].Clone as TJSONObject);
      end;
    end;
  finally
    PastedArray.Free;
  end;
end;

procedure TActionPatternsFrame.UpdateGrid;
var
  NumPatterns: Integer = 0;
  I: Integer;
  Range: TGridRect;
begin
  if EditedPatterns <> nil then
    NumPatterns := EditedPatterns.Count;

  PatternsStringGrid.BeginUpdate;
  PatternsStringGrid.RowCount := NumPatterns + 2;
  PatternsStringGrid.Cells[0, 0] := SkillStr;
  PatternsStringGrid.Cells[1, 0] := ConditionStr;
  PatternsStringGrid.Cells[2, 0] := RatingStr;

  if EditedPatterns <> nil then
    for I := 0 to EditedPatterns.Count - 1 do
      UpdatePatternCells(I+1, EditedPatterns.Objects[I]);

  PatternsStringGrid.Cells[0, NumPatterns + 1] := '';
  PatternsStringGrid.Cells[1, NumPatterns + 1] := AddNewStr;
  PatternsStringGrid.Cells[2, NumPatterns + 1] := '';

  Range := PatternsStringGrid.Selection;
  Range.Left := 0;
  Range.Right := 2;
  PatternsStringGrid.Selection := Range;

  PatternsStringGrid.EndUpdate(true);
end;

end.

